#include <d3dx9.h>

#include "GraphicsManager.h"
#include "Math\Color.h"
#include "Core.h"
#include "InputManager.h"
#include "InputDefs.h"
#include "Math\Matrix44.h"

#include <assert.h>

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_DIFFUSE)

struct CUSTOMVERTEX
{
	float x, y, z;
	DWORD color;
	static unsigned int getFlags()
	{
		return D3DFVF_CUSTOMVERTEX;
	}
};

CGraphicsManager::CGraphicsManager(void)
{
}


CGraphicsManager::~CGraphicsManager(void)
{
}

void
CGraphicsManager::DeInit()
{
}

void
CGraphicsManager::Update()
{
}

bool
CGraphicsManager::Init (HWND hWnd, bool fullscreenMode, uint32 widthScreen, uint32 heightScreen)
{
	//LOGGER->AddNewLog(ELL_INFORMATION, "RenderManager:: Inicializando la libreria Direct3D");
	// Create the D3D object.
	m_pD3D = Direct3DCreate9( D3D_SDK_VERSION );
	assert(m_pD3D);
	m_bIsOk = m_pD3D != NULL;

	if (m_bIsOk !=NULL)
	{
		// Set up the structure used to create the D3DDevice
		D3DPRESENT_PARAMETERS d3dpp;
		ZeroMemory( &d3dpp, sizeof(d3dpp) );


		if(fullscreenMode)
		{
			d3dpp.Windowed          = FALSE;
			d3dpp.BackBufferWidth   = widthScreen;
			d3dpp.BackBufferHeight  = heightScreen;
			d3dpp.BackBufferFormat = D3DFMT_R5G6B5;
		}
		else
		{
			d3dpp.Windowed          = TRUE;
			d3dpp.BackBufferFormat	= D3DFMT_UNKNOWN;
		}

		d3dpp.SwapEffect			= D3DSWAPEFFECT_DISCARD;
		d3dpp.AutoDepthStencilFormat	= D3DFMT_D16;
		d3dpp.EnableAutoDepthStencil	= TRUE;
		d3dpp.AutoDepthStencilFormat	= D3DFMT_D24S8;
		d3dpp.Flags			= D3DPRESENTFLAG_DISCARD_DEPTHSTENCIL;
		d3dpp.PresentationInterval		= D3DPRESENT_INTERVAL_IMMEDIATE;


		// Create the D3DDevice
		m_bIsOk = !FAILED(m_pD3D->CreateDevice(	D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
			hWnd,D3DCREATE_HARDWARE_VERTEXPROCESSING, &d3dpp, &m_pD3DDevice ) );

		if (!m_bIsOk)
		{
			m_bIsOk = !FAILED(m_pD3D->CreateDevice(	D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
				D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp, &m_pD3DDevice ) );

			if (m_bIsOk)
			{
			  //LOGGER->AddNewLog(	ELL_INFORMATION, 
				//"RenderManager::D3DCREATE_SOFTWARE_VERTEXPROCESSING");
			}
		}
		else
		{
			//LOGGER->AddNewLog(	ELL_INFORMATION, 
				//"RenderManager:: D3DCREATE_HARDWARE_VERTEXPROCESSING");
		}

		if (m_bIsOk)
		{
			// Turn off culling, so we see the front and back of the triangle
			m_pD3DDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
			m_pD3DDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
			m_pD3DDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);
			m_pD3DDevice->SetSamplerState(1, D3DSAMP_MINFILTER, D3DTEXF_LINEAR);
			m_pD3DDevice->SetSamplerState(1, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
			m_pD3DDevice->SetSamplerState(1, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR);

			m_pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
			m_pD3DDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
			m_pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
			m_pD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );


			m_pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW);
			m_pD3DDevice->SetRenderState( D3DRS_ZENABLE,D3DZB_TRUE);
			m_pD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
			m_pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE);

			// Turn off D3D lighting, since we are providing our own vertex colors
			m_pD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

			if (fullscreenMode)
			{
				m_uWidth	= widthScreen;
				m_uHeight	= heightScreen;	
			}
			else
			{
				GetWindowRect(hWnd);
			}
			//LOGGER->AddNewLog(ELL_INFORMATION,
				//"RenderManager:: La resolucion de pantalla es (%dx%d)",m_uWidth,m_uHeight);
			
		}

		if (!m_bIsOk)
		{
			//std::string msg_error = "Rendermanager::Init-> Error al inicializar Direct3D";
			//LOGGER->AddNewLog(ELL_ERROR, msg_error.c_str());
			//Release();
			//throw CException(__FILE__, __LINE__, msg_error);
		}

		m_BackbufferColor_debug = CColor(0.3,0.3,1);
		m_BackbufferColor_release = colBLACK;

		m_bPaintSolid = true;

		m_yaw = 0.0f;
		m_pitch = 0.0f;
		m_speed = 0.1f;
		m_pos = Vect3f(5,5,5);

		return m_bIsOk;
	}
}

void
CGraphicsManager::GetWindowRect( HWND hwnd )
{
	RECT rec_window;
	GetClientRect(	hwnd, &rec_window);
	m_uWidth = rec_window.right - rec_window.left;
	m_uHeight = rec_window.bottom - rec_window.top;
}

void
CGraphicsManager::Release()
{
}

void
CGraphicsManager::SetupMatrices(CCamera *inCamera)
{

	D3DXMATRIX m_matView;
	D3DXMATRIX m_matProject;

	if(0 == inCamera)
	{
		D3DXVECTOR3 l_LookAt(0.0f, 0.0f, 0.0f);
		D3DXVECTOR3 l_Eye(5.0f, 1.0f, 5.0f);
		D3DXVECTOR3 l_VUP(0.0f, 1.0f, 0.0f);

		D3DXMatrixLookAtLH(&m_matView, &l_Eye, &l_LookAt, &l_VUP);
		D3DXMatrixPerspectiveFovLH(&m_matProject, 45.0f * D3DX_PI / 180.0f, 1.0f, 1.0f, 100.0f);
	}
	else
	{
		Vect3f eye = inCamera->GetEye();
		D3DXVECTOR3 l_Eye(eye.x, eye.y, eye.z);
		
		Vect3f lookat = inCamera->GetLookAt();
		D3DXVECTOR3 l_LookAt(lookat.x, lookat.y, lookat.z);
		
		Vect3f vup = inCamera->GetVecUp();
		D3DXVECTOR3 l_VUP(vup.x, vup.y, vup.z);
		D3DXMatrixLookAtLH(&m_matView, &l_Eye, &l_LookAt, &l_VUP);
		
		D3DXMatrixPerspectiveFovLH(&m_matProject, inCamera->GetFov(), inCamera->GetAspectRatio(), inCamera->GetZn(), inCamera->GetZf());
	}
	
	m_Frustum.Update(m_matView * m_matProject);
	m_pD3DDevice->SetTransform( D3DTS_VIEW, &m_matView );
	m_pD3DDevice->SetTransform( D3DTS_PROJECTION, &m_matProject );


	//D3DXMATRIX m_matView;
	//D3DXMATRIX m_matProject;

	//Vect3f up(0,1,0);
	//Vect3f dir(cos(m_yaw)+sin(m_pitch),0,cos(m_pitch)+sin(m_yaw));
	//Vect3f norm = (dir+D3DX_PI/2)*D3DX_PI/180;
	//norm.y = 0;

	//m_yaw += CCore::GetSingleton().GetinputManager()->GetMouseDelta().x;
	//m_pitch += CCore::GetSingleton().GetinputManager()->GetMouseDelta().y;
	//if (CCore::GetSingleton().GetinputManager()->IsDown(IDV_KEYBOARD, KEY_UP))
	//	m_pos += dir*m_speed;
	//if (CCore::GetSingleton().GetinputManager()->IsDown(IDV_KEYBOARD, KEY_DOWN))
	//	m_pos -= dir*m_speed;
	//if (CCore::GetSingleton().GetinputManager()->IsDown(IDV_KEYBOARD, KEY_RIGHT))
	//	m_pos += norm*m_speed;
	//if (CCore::GetSingleton().GetinputManager()->IsDown(IDV_KEYBOARD, KEY_LEFT))
	//	m_pos -= norm*m_speed;

	////Setup Matrix view
	//D3DXVECTOR3 l_Eye(m_pos.x, m_pos.y, m_pos.z), l_LookAt(cos(m_yaw)+sin(m_pitch),0,cos(m_pitch)+sin(m_yaw)), l_VUP(0.0f,1.0f,0.0f);
	//l_LookAt += l_Eye;
	//D3DXMatrixLookAtLH( &m_matView, &l_Eye, &l_LookAt, &l_VUP);

	////Setup Matrix projection
	//D3DXMatrixPerspectiveFovLH( &m_matProject, 45.0f * D3DX_PI / 180.0f, 1.0f, 1.0f, 100.0f );

	///*if(!camera)
	//{*/
	//	//Set default view and projection matrix

	//	//Setup Matrix view
	//	//D3DXVECTOR3 l_Eye(5.0f,5.0f,5.0f), l_LookAt(0.0f,0.0f,0.0f), l_VUP(0.0f,1.0f,0.0f);
	//	//D3DXMatrixLookAtLH( &m_matView, &l_Eye, &l_LookAt, &l_VUP);

	//	//Setup Matrix projection
	//	//D3DXMatrixPerspectiveFovLH( &m_matProject, 45.0f * D3DX_PI / 180.0f, 1.0f, 1.0f, 100.0f );
	////}
	////else
	////{
	////	Vect3f eye = camera->GetEye();
	////	D3DXVECTOR3 l_Eye(eye.x, eye.y, eye.z);
	////	Vect3f lookat = camera->GetLookAt();
	////	D3DXVECTOR3 l_LookAt(lookat.x, lookat.y, lookat.z);
	////	Vect3f vup = camera->GetVecUp();
	////	D3DXVECTOR3 l_VUP(vup.x, vup.y, vup.z);
	////	//Setup Matrix view
	////	D3DXMatrixLookAtLH( &m_matView, &l_Eye, &l_LookAt, &l_VUP);

	////	//Setup Matrix projection
	////	D3DXMatrixPerspectiveFovLH(	&m_matProject, camera->GetFov(), camera->GetAspectRatio(),
	////		camera->GetZn(), camera->GetZf());
	////}

	////m_Frustum.Update( m_matView * m_matProject );

	//m_pD3DDevice->SetTransform( D3DTS_VIEW, &m_matView );
	//m_pD3DDevice->SetTransform( D3DTS_PROJECTION, &m_matProject );
}



void
CGraphicsManager::EndRendering ()
{
	m_pD3DDevice->EndScene();
	// Present the backbuffer contents to the display
	m_pD3DDevice->Present( NULL, NULL, NULL, NULL );
}

void
CGraphicsManager::BeginRendering ()
{

#ifdef _DEBUG // Clear the backbuffer to a blue color in a Debug mode
	uint32 red		= (uint32) (m_BackbufferColor_debug.GetRed() * 255);
	uint32 green	= (uint32) (m_BackbufferColor_debug.GetGreen() * 255);
	uint32 blue		= (uint32) (m_BackbufferColor_debug.GetBlue()* 255);
	m_pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(red, green, blue), 1.0f, 0 );
#else // Clear the backbuffer to a black color in a Release mode
	uint32 red		= (uint32) (m_BackbufferColor_release.GetRed() * 255);
	uint32 green	= (uint32) (m_BackbufferColor_release.GetGreen() * 255);
	uint32 blue		= (uint32) (m_BackbufferColor_release.GetBlue()* 255);
	m_pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(red, green, blue), 1.0f, 0 );
#endif


	// Begin the scene
	HRESULT hr=m_pD3DDevice->BeginScene();
	assert( SUCCEEDED( hr ) );
	m_pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW);
	m_pD3DDevice->SetRenderState( D3DRS_ZENABLE,D3DZB_TRUE);
	m_pD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL);
	m_pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE);
	m_pD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	m_pD3DDevice->SetRenderState( D3DRS_DITHERENABLE, TRUE );
	m_pD3DDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE );

	if(m_bPaintSolid)
	{
		m_pD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID  );
	}
	else
	{
		m_pD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME  );
	}
}

void
CGraphicsManager::DrawLine ( const Vect3f &PosA, const Vect3f &PosB, CColor Color)
{
	DWORD color_aux = Color.GetUint32Argb();

	CUSTOMVERTEX v[2] =
	{
		{PosA.x, PosA.y, PosA.z, color_aux},
		{PosB.x, PosB.y, PosB.z, color_aux},
	};

	m_pD3DDevice->SetTexture(0,NULL);
	m_pD3DDevice->SetFVF(CUSTOMVERTEX::getFlags());
	m_pD3DDevice->DrawPrimitiveUP( D3DPT_LINELIST,1, v,sizeof(CUSTOMVERTEX));
}

void
CGraphicsManager::DrawAxis(float Size)
{
	DrawLine(Vect3f(0,0,0), Vect3f(Size,0,0), colRED);
	DrawLine(Vect3f(0,0,0), Vect3f(0,Size,0), colBLUE);
	DrawLine(Vect3f(0,0,0), Vect3f(0,0,Size), colGREEN);
}

void
CGraphicsManager::DrawCube(float Size, CColor color)
{
	float Size2 = Size/2;

	DrawLine(Vect3f(-Size2,-Size2,-Size2), Vect3f(Size2,-Size2,-Size2), color);
	DrawLine(Vect3f(Size2,-Size2,-Size2), Vect3f(Size2,Size2,-Size2), color);
	DrawLine(Vect3f(Size2,Size2,-Size2), Vect3f(-Size2,Size2,-Size2), color);
	DrawLine(Vect3f(-Size2,Size2,-Size2), Vect3f(-Size2,-Size2,-Size2), color);

	DrawLine(Vect3f(-Size2,-Size2,Size2), Vect3f(Size2,-Size2,Size2), color);
	DrawLine(Vect3f(Size2,-Size2,Size2), Vect3f(Size2,Size2,Size2), color);
	DrawLine(Vect3f(Size2,Size2,Size2), Vect3f(-Size2,Size2,Size2), color);
	DrawLine(Vect3f(-Size2,Size2,Size2), Vect3f(-Size2,-Size2,Size2), color);

	DrawLine(Vect3f(-Size2,-Size2,-Size2), Vect3f(-Size2,-Size2,Size2), color);
	DrawLine(Vect3f(Size2,-Size2,-Size2), Vect3f(Size2,-Size2,Size2), color);
	DrawLine(Vect3f(Size2,Size2,-Size2), Vect3f(Size2,Size2,Size2), color);
	DrawLine(Vect3f(-Size2,Size2,-Size2), Vect3f(-Size2,Size2,Size2), color);
}

void
CGraphicsManager::DrawBox(float SizeX, float SizeY, float SizeZ, CColor color)
{
	DrawLine(Vect3f(0,0,0), Vect3f(SizeX,0,0), color);
	DrawLine(Vect3f(SizeX,0,0), Vect3f(SizeX,SizeY,0), color);
	DrawLine(Vect3f(SizeX,SizeY,0), Vect3f(0,SizeY,0), color);
	DrawLine(Vect3f(0,SizeY,0), Vect3f(0,0,0), color);

	DrawLine(Vect3f(0,0,SizeZ), Vect3f(SizeX,0,SizeZ), color);
	DrawLine(Vect3f(SizeX,0,SizeZ), Vect3f(SizeX,SizeY,SizeZ), color);
	DrawLine(Vect3f(SizeX,SizeY,SizeZ), Vect3f(0,SizeY,SizeZ), color);
	DrawLine(Vect3f(0,SizeY,SizeZ), Vect3f(0,0,SizeZ), color);

	DrawLine(Vect3f(0,0,0), Vect3f(0,0,SizeZ), color);
	DrawLine(Vect3f(SizeX,0,0), Vect3f(SizeX,0,SizeZ), color);
	DrawLine(Vect3f(SizeX,SizeY,0), Vect3f(SizeX,SizeY,SizeZ), color);
	DrawLine(Vect3f(0,SizeY,0), Vect3f(0,SizeY,SizeZ), color);
}

void
CGraphicsManager::DrawGrid(float Size, CColor color, int GridX, int GridZ)
{
	float Size2 = Size/2;
	float stepX = Size/GridX;
	float stepZ = Size/GridZ;

	for (int i = 0; i<=GridZ; ++i)
	{
		DrawLine(Vect3f(-Size2,0,-Size2+stepZ*i), Vect3f(Size2,0,-Size2+stepZ*i), color);
	}
	for (int i = 0; i<=GridX; ++i)
	{
		DrawLine(Vect3f(-Size2+stepX*i,0,Size2), Vect3f(-Size2+stepX*i,0,-Size2), color);
	}
}

void
CGraphicsManager::DrawPlane(float Size, const Vect3f& normal, float distance, CColor color, int GridX, int GridZ)
{
	float A,B,C,D;
	A = normal.x;
	B = normal.y;
	C = normal.z;
	D = distance;

	Vect3f pointA, pointB;
	if( C!= 0)
	{
		pointA = Vect3f(0.f,0.f, -D/C);
		pointB = Vect3f(1.f,1.f, (D-A-B)/C);
	}
	else if( B!= 0)
	{
		pointA = Vect3f(0.f,-D/B, 0.f);
		pointB = Vect3f(1.f,(-D-A-C)/B,1.f);
	}
	else if( A != 0)
	{
		pointA = Vect3f(-D/A,0.f, 0.f);
		pointB = Vect3f((-D-B-C)/A,1.f,1.f);
	}

	Vect3f vectorA = pointB - pointA;
	vectorA.Normalize();
	Vect3f vectorB;
	vectorB = vectorA ^ normal;
	vectorB.Normalize();
	Vect3f initPoint = normal*distance;

	Vect3f initPointA = initPoint - vectorB*Size*0.5;
	for(int b=0;b<=GridX;++b)
	{
		DrawLine(initPointA + vectorA*Size*0.5, initPointA - vectorA*Size*0.5, color);
		initPointA += vectorB*Size/(float)GridX;
	}
	initPointA = initPoint - vectorA*Size*0.5;

	for(int b=0;b<=GridX;++b)
	{
		DrawLine(initPointA + vectorB*Size*0.5, initPointA - vectorB*Size*0.5, color);
		initPointA += vectorA*Size/(float)GridX;
	}
}

void
CGraphicsManager::SetTransform(D3DXMATRIX& matrix)
{
	m_pD3DDevice->SetTransform(D3DTS_WORLD, &matrix);
}

void
CGraphicsManager::SetTransform(Mat44f& m)
{
	D3DXMATRIX aux(	m.m00, m.m10, m.m20, m.m30
				, m.m01, m.m11, m.m21, m.m31
				, m.m02, m.m12, m.m22, m.m32
				, m.m03, m.m13, m.m23, m.m33);
	m_pD3DDevice->SetTransform(D3DTS_WORLD, &aux);
}